﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace com.ptrampert.ObjectContexts.EntityFramework
{
    /// <summary>
    /// Abstract object context with an underlying EntityFramework DbContext.
    /// </summary>
    /// <typeparam name="C">The type of DbContext being used.</typeparam>
    public abstract class ObjectContextBase<C> : IObjectContext where C : DbContext, new()
    {
        /// <summary>
        /// This is the underlying entity framework DbContext object.
        /// </summary>
        protected C Context { get; set; }

        /// <summary>
        /// Returns whether or not the context has been disposed.
        /// </summary>
        public bool Disposed { get; private set; }

        /// <summary>
        /// Create a new object context.
        /// </summary>
        public ObjectContextBase()
        {
            Context = new C();
            Disposed = false;
        }

        /// <summary>
        /// Create a new ObjectContextBase with the specified DbContext.
        /// </summary>
        /// <param name="context">The DbContext to wrap.</param>
        public ObjectContextBase(C context)
        {
            Context = context;
            Disposed = false;
        }

        /// <summary>
        /// Get all objects of type T.
        /// </summary>
        /// <typeparam name="T">Type of object to get.</typeparam>
        /// <returns>All objects of type T.</returns>
        public IQueryable<T> All<T>() where T : class
        {
            return Context.Set<T>();
        }

        /// <summary>
        /// Find the object of type T identified by ids.
        /// </summary>
        /// <typeparam name="T">Type of object to find.</typeparam>
        /// <param name="ids">The desired object's identifying values.</param>
        /// <returns>Object of type T, or null if not found.</returns>
        public T Find<T>(params object[] ids) where T : class
        {
            return Context.Set<T>().Find(ids);
        }

        /// <summary>
        /// Find all objects that match the predicate.
        /// </summary>
        /// <typeparam name="T">Type of objects to find.</typeparam>
        /// <param name="predicate">Predicate describing the matching condition.</param>
        /// <returns>All objects that match the predicate.</returns>
        public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return Context.Set<T>().Where(predicate);
        }

        /// <summary>
        /// Update an object.
        /// </summary>
        /// <typeparam name="T">Type of object being updated.</typeparam>
        /// <param name="obj">The object being updated.</param>
        public void Update<T>(T obj) where T : class
        {
            Context.Set<T>().Attach(obj);
            Context.Entry(obj).State = EntityState.Modified;
        }

        /// <summary>
        /// Add an object.
        /// </summary>
        /// <typeparam name="T">Type of object being added.</typeparam>
        /// <param name="obj">The object being added.</param>
        public void Add<T>(T obj) where T : class
        {
            Context.Set<T>().Add(obj);
        }

        /// <summary>
        /// Delete an object.
        /// </summary>
        /// <typeparam name="T">Type of object being deleted.</typeparam>
        /// <param name="obj">Object being deleted.</param>
        public void Delete<T>(T obj) where T : class
        {
            Context.Set<T>().Remove(obj);
        }

        /// <summary>
        /// Delete an object.
        /// </summary>
        /// <typeparam name="T">Type of object being deleted.</typeparam>
        /// <param name="ids">Identifiers for the object to delete.</param>
        public void Delete<T>(params object[] ids) where T : class
        {
            T obj = Context.Set<T>().Find(ids);
            if (obj != null)
                Context.Set<T>().Remove(obj);
        }

        /// <summary>
        /// Delete all objects that match the predicate.
        /// </summary>
        /// <typeparam name="T">Type of objects being deleted.</typeparam>
        /// <param name="predicate">Predicate describing the matching condition.</param>
        public void Delete<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            foreach (T obj in Context.Set<T>().Where(predicate))
            {
                Context.Set<T>().Remove(obj);
            }
        }

        /// <summary>
        /// Commit changes.
        /// </summary>
        public void Commit()
        {
            Context.SaveChanges();
        }

        public Task<IQueryable<T>> AllAsync<T>() where T : class
        {
            return Task.Run(() => All<T>());
        }

        public Task<T> FindAsync<T>(params object[] ids) where T : class
        {
            return Task.Run(() => Find<T>(ids));
        }

        public Task<IQueryable<T>> FindAsync<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return Task.Run(() => Find(predicate));
        }

        public Task UpdateAsync<T>(T obj) where T : class
        {
            return Task.Run(() => Update(obj));
        }

        public Task AddAsync<T>(T obj) where T : class
        {
            return Task.Run(() => Add(obj));
        }

        public Task DeleteAsync<T>(T obj) where T : class
        {
            return Task.Run(() => Delete(obj));
        }

        public Task DeleteAsync<T>(params object[] ids) where T : class
        {
            return Task.Run(() => Delete<T>(ids));
        }

        public Task DeleteAsync<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return Task.Run(() => Delete(predicate));
        }

        public Task CommitAsync()
        {
            return Task.Run(() => Commit());
        }

        /// <summary>
        /// Dispose the context.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose the context.
        /// </summary>
        /// <param name="disposing">Whether or not this was called by a call to Dispose()</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                Context.Dispose();
                Disposed = true;
            }
        }
    }
}
